﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;
using System.Xml;
using System.Drawing;
using Clandestine.Entities;
using Clandestine;
using System.Windows.Forms;

namespace Slick
{
    public class EntityType
    {
        public string FullName;
        public string FriendlyName;
        public bool Trigger;
    }

    public static class Entities
    {
        public static List<Entity> AllEntities = new List<Entity>();

        private static object selectedEntityLockBlob = new object();
        private static Entity selectedEntity = null;

        public static object SelectedEntityTypeLockBlob = new object();
        public static EntityType SelectedEntityType = null;
        
        public static EntityType[] ValidEntitiesList;

        public static Dictionary<Trigger, Sprite> TriggerSprites = new Dictionary<Trigger, Sprite>();

        public static void AddEntity(Entity ent)
        {
            lock (AllEntities)
            {
                AllEntities.Add(ent);
                Slick.Program.fEntityProperties.RebuildEntityNameList();
            }
        }

        static Entities()
        {
            List<EntityType> validEntityTypes = new List<EntityType>();

            // Woo, reflection!
            // (stolen from mikey & adapted a little, eheh.)
            Type[] types = Assembly.GetAssembly(typeof(Clandestine.Entities.Entity)).GetTypes();
            foreach (Type t in types)
            {
                if (t.FullName == "Clandestine.MapPlayer")
                    continue; // mapplayer isn't an entity. ...but it might be!
                if (t.IsAbstract)
                    continue;

                if (t.IsSubclassOf(typeof(Clandestine.Entities.Entity)))
                {
                    EntityType et = new EntityType();
                    et.FullName = t.FullName;

                    et.Trigger = t.IsSubclassOf(typeof(Clandestine.Entities.Trigger));

                    if (et.FullName.StartsWith("Clandestine.Entities."))
                        et.FriendlyName = et.FullName.Substring("Clandestine.Entities.".Length);
                    else
                        et.FriendlyName = et.FullName;

                    validEntityTypes.Add(et);
                }
            }

            ValidEntitiesList = validEntityTypes.ToArray();
        }

        public static void SelectEntity(Entity ent)
        {
            lock (selectedEntityLockBlob)
            {
                if (selectedEntity != null && selectedEntity.GetType().IsSubclassOf(
                    typeof(Clandestine.Entities.Trigger)))
                {
                    lock (Entities.TriggerSprites)
                        if (Entities.TriggerSprites.ContainsKey((Trigger)selectedEntity))
                            Entities.TriggerSprites[((Trigger)selectedEntity)].IgnoreGlobalColor = false;
                }
                else if (selectedEntity != null && selectedEntity.Sprite != null)
                    selectedEntity.Sprite.IgnoreGlobalColor = false;

                selectedEntity = ent;
                Program.fEntityProperties.Invoke(new ThreadStart(delegate
                {
                    Program.fEntityProperties.pgrid.SelectedObject = ent;
                    Program.fEntityProperties.AllowEntityChange = false;
                    Program.fEntityProperties.cmbEntities.SelectedItem = ent;
                    Program.fEntityProperties.AllowEntityChange = true;
                }));

                if (selectedEntity != null && selectedEntity.GetType().IsSubclassOf(typeof(Clandestine.Entities.Trigger)))
                    lock (Entities.TriggerSprites)
                        Entities.TriggerSprites[((Trigger)selectedEntity)].IgnoreGlobalColor = true;
                else if (selectedEntity != null)
                    selectedEntity.Sprite.IgnoreGlobalColor = true;
            }
        }

        public static Entity GetSelectedEntity()
        {
            lock (selectedEntityLockBlob)
                return selectedEntity;
        }

        public static void AddTrigger(Trigger ent)
        {
            Sprite rep = new Sprite(Texture.GetTexture("white.png"));
            rep.ScaleX = ent.Size.Width;
            rep.ScaleY = ent.Size.Height;
            rep.Position = ent.Position;
            rep.Layer = ((float)Program.fPreviewControl.updLayer.Value) + 0.01f;
            rep.Color = ent.EditorColor;
            lock (Entities.TriggerSprites)
                Entities.TriggerSprites.Add(ent, rep);
        }

        public static void LoadEntities(XmlTextReader xml)
        {
            // Go through till </entities>
            while (!((xml.NodeType == XmlNodeType.EndElement) && (xml.Name == "entities")))
            {
                // New entity? -- Should be...
                if ((xml.NodeType == XmlNodeType.Element) && (xml.Name == "entity"))
                {
                    Entity e = (Entity)System.Activator.CreateInstance(xml.GetAttribute("assembly"), xml.GetAttribute("type")).Unwrap();

                    // Go through and add properties
                    while (!((xml.NodeType == XmlNodeType.EndElement) && (xml.Name == "entity")))
                    {
                        if (xml.NodeType == XmlNodeType.Element)
                        {
                            // Size
                            if (xml.Name == "size")
                                if (e.SupportsResizing)
                                    e.Size = new Size(int.Parse(xml.GetAttribute("width")), int.Parse(xml.GetAttribute("height")));
                                else
                                    Log.e("Map file tries to set .Size of an Entity ({0}) that does not support resizing!", e.GetType().FullName);

                            // Position
                            if (xml.Name == "position")
                                e.Position = new Point(int.Parse(xml.GetAttribute("x")), int.Parse(xml.GetAttribute("y")));
                            
                            // Generic property
                            if (xml.Name == "property")
                            {
                                string name = xml.GetAttribute("name");
                                string strValue = xml.GetAttribute("value");
                                object value = null;

                                // Does the property even exist?
                                if (e.GetType().GetProperty(name) == null)
                                {
                                    Log.w("Map is trying to set property '" + name + "' to '" + strValue + "' on entity '" + e.Name + "' of type "
                                        + e.GetType().Name + " -- however, the object has no such property.");
                                    Log.w("The map is probably out of date.  Property ignored.  Please check the entity's remaining properties are correct,"
                                        + " and then save.");
                                }
                                else
                                {
                                    if (e.GetType().GetProperty(name).PropertyType == typeof(Rectangle))
                                    {
                                        //haxy rectangle loading! :) 
                                        //A string that contains the position, width, and height of this Rectangle structure,for example, {X=a, Y=b, Width=c, Height=d} 
                                        string[] sc = strValue.Split(new string[] { " ; ", ",", "}", "{" }, StringSplitOptions.RemoveEmptyEntries);
                                        int a = int.Parse(sc[0].Substring(2, sc[0].Length - 2));
                                        int b = int.Parse(sc[1].Substring(2, sc[1].Length - 2));
                                        int c = int.Parse(sc[2].Substring(6, sc[2].Length - 6));
                                        int d = int.Parse(sc[3].Substring(7, sc[3].Length - 7));
                                        value = new Rectangle(a, b, c, d);
                                    }
                                    else if (e.GetType().GetProperty(name).PropertyType.IsEnum)
                                        value = Enum.Parse(e.GetType().GetProperty(name).PropertyType, strValue);
                                    else
                                        value = Convert.ChangeType(strValue, e.GetType().GetProperty(name).PropertyType);
                                    e.GetType().GetProperty(name).SetValue(e, value, null);
                                }
                            }
                        }
                        xml.Read();
                    }

                    AddEntity(e);
                    if (e.GetType().IsSubclassOf(typeof(Trigger)))
                        Entities.AddTrigger((Trigger)e
                            );
                }

                xml.Read();
            }
        }

        public static void SaveEntities(XmlTextWriter xml, int minx, int miny)
        {
            xml.WriteStartElement("entities");

            // Go through each entity
            lock (AllEntities)
            {
                for (int i = 0; i < AllEntities.Count; i++)
                {
                    xml.WriteStartElement("entity");
                    xml.WriteAttributeString("type", AllEntities[i].GetType().FullName);
                    xml.WriteAttributeString("assembly", AllEntities[i].GetType().Assembly.FullName);

                    // Get all the properties, and save the writable ones
                    PropertyInfo[] properties = AllEntities[i].GetType().GetProperties();
                    foreach (PropertyInfo p in properties)
                    {
                        // Only save the write-able ones
                        if (p.CanWrite)
                        {
                            // Watch out for special cases
                            switch (p.Name)
                            {
                                // Stupid struct >.>
                                case "Position":
                                    Point pos = (Point)p.GetValue(AllEntities[i], null);
                                    xml.WriteStartElement("position");
                                    xml.WriteAttributeString("x", (pos.X - minx).ToString());
                                    xml.WriteAttributeString("y", (pos.Y - miny).ToString());
                                    xml.WriteEndElement();
                                    break;

                                // Only save if .SupportsResizing
                                case "Size":
                                    if (AllEntities[i].SupportsResizing)
                                    {
                                        Size size = (Size)p.GetValue(AllEntities[i], null);
                                        xml.WriteStartElement("size");
                                        xml.WriteAttributeString("width", size.Width.ToString());
                                        xml.WriteAttributeString("height", size.Height.ToString());
                                        xml.WriteEndElement();
                                    }
                                    break;

                                // Generic saving
                                default:
                                    xml.WriteStartElement("property");
                                    xml.WriteAttributeString("name", p.Name);
                                    xml.WriteAttributeString("value", p.GetValue(AllEntities[i], null).ToString());
                                    xml.WriteEndElement();
                                    break;
                            }
                        }
                    }

                    xml.WriteEndElement();
                }
            }

            xml.WriteEndElement();
        }

        internal static void DeleteSelectedEntity()
        {
            if (GetSelectedEntity() != null)
            {
                lock (AllEntities)
                {
                    lock (selectedEntityLockBlob)
                    {
                        if (selectedEntity != null)
                        {
                            AllEntities.Remove(selectedEntity);
                            selectedEntity.Dispose();
                            selectedEntity = null;
                        }
                    }
                }
                Program.fEntityProperties.RebuildEntityNameList();
            }
        }
    }
}
